﻿/**
   \file hexfile.c

   \author G. Icking-Konert

   \brief implementation of routines for HEX, S19 and table files

   implementation of routines for importing and exporting Motorola S19 and Intel HEX files,
   as well as plain ASCII tables.
   (format descriptions under http://en.wikipedia.org/wiki/SREC_(file_format) or
   http://www.keil.com/support/docs/1584.htm).
*/

// include files
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <inttypes.h>
#include <ctype.h>
#include <time.h>
// #include <assert.h>
#include <errno.h>
#include "hexfile.h"
#include "global.h"
#include "misc.h"


/// @brief 格式化
/// @param[in]  image   pointer to memory image.
/// @return 
static void MemoryImage_format(MemoryImage_s *image)
{
    MemoryEntry_s* p = image->firstMemoryEntries;
    while (p != NULL)
    {
      if(p->capacityRsrv > 0)
      {
        p->data = realloc(p->data,p->capacityUsed);
        p->capacityRsrv = p->capacityUsed - p->capacityUsed;
      }
      p = p->next;
    }
}

/**
  \fn void import_file_s19(const char *filename, MemoryImage_s *image, const uint8_t verbose)

  \param[in]  filename    full name of file to read 
  \param      image       pointer to memory image. Must be initialized. Existing content is overwritten
  \param[in]  verbose     verbosity level (0=MUTE, 1=SILENT, 2=INFORM, 3=CHATTY)
  \return                0 on success, others on error

  Read Motorola s19 hexfile into memory image. For description of
  Motorola S19 file format see http://en.wikipedia.org/wiki/SREC_(file_format)
*/
bool import_file_s19(const wchar_t *filename, MemoryImage_s *image) {

  FILE      *fp;
#if HEXFILE_DEBUG_MSG
  wchar_t*   werror;
  size_t     werrorlen;
#endif
  // open file to read
  if (!(fp = _wfopen(filename, L"rb"))) {
    MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
    werrorlen = mbstowcs(NULL,strerror(errno),0);
    werror = (wchar_t*)malloc((werrorlen+1)*sizeof(wchar_t));
    mbstowcs(werror,strerror(errno),werrorlen+1);
    Error(L"Failed to open file %s with error [%s]", filename, werror);
    free(werror);
#endif
    return 1;
  }


  //=====================
  // start data import
  //=====================

  char              line[STRLEN], tmp[STRLEN];
  int               linecount = 0, idx, len;
  uint8_t           type, chkRead, chkCalc;
  MEMIMAGE_ADDR_T   address = 0; 
  int               value = 0;

  // read data line by line
  while (fgets(line, STRLEN, fp)) {

    // increase line counter
    linecount++;

    // check 1st char (must be 'S')
    if (line[0] != 'S') {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      Error(L"Line %u in Motorola S-record: line does not start with 'S'", linecount);
#endif
      return 1;
    }

    // record type
    type = line[1]-48;

    // skip if line contains no data, i.e. line doesn't start with S1, S2 or S3
    if ((type != 1) && (type != 2) && (type != 3))
      continue;

    // record length (address + data + checksum)
    sprintf(tmp,"0x00");
    strncpy(tmp+2, line+2, 2);
    sscanf(tmp, "%x", &value);
    len = value;
    chkCalc = value;

    // address (S1=16bit, S2=24bit, S3=32bit)
    address = 0;
    for (int i=0; i<type+1; i++) {
      sprintf(tmp,"0x00");
      tmp[2] = line[4+(i*2)];
      tmp[3] = line[5+(i*2)];
      sscanf(tmp, "%x", &value);
      address *= (uint64_t) 256;
      address += (uint64_t) value;
      chkCalc += (uint8_t) value;
    }

    // read record data
    idx = 6+(type*2);                   // start at position 8, 10, or 12, depending on record type
    len = len-1-(1+type);               // substract chk and address length
    for (MEMIMAGE_ADDR_T i=0; i<len; i++) {
      
      // get next value
      sprintf(tmp,"0x00");
      strncpy(tmp+2, line+idx, 2);      // get next 2 chars as string
      sscanf(tmp, "%x", &value);        // interpret as hex data

      // store data byte in memory image
      MemoryImage_addData(image, address+i, (uint8_t) value);

      chkCalc += (uint8_t) value;       // increase checksum
      idx+=2;                           // advance 2 chars in line
    }

    // read checksum
    sprintf(tmp,"0x00");
    strncpy(tmp+2, line+idx, 2);
    sscanf(tmp, "%x", &value);
    chkRead = (uint8_t) value;

    // assert checksum (0xFF xor (sum over all except record type)
    chkCalc ^= 0xFF;                 // invert checksum
    if (chkCalc != chkRead) {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      Error(L"Line %u in Motorola S-record: checksum error (0x%02" PRIX8 " vs. 0x%02" PRIX8 ")", linecount, (uint8_t) chkRead, (uint8_t) chkCalc);
#endif
      return 1;
    }

  } // while !EOF

  //=====================
  // end data import
  //=====================

  MemoryImage_format(image);
  // close file again
  fclose(fp);
  return 0;
} // import_file_s19()



/**
  \fn void import_file_ihx(const char *filename, MemoryImage_s *image, const uint8_t verbose)

  \param[in]  filename    full name of file to read 
  \param      image       pointer to memory image. Must be initialized. Existing content is overwritten
  \param[in]  verbose     verbosity level (0=MUTE, 1=SILENT, 2=INFORM, 3=CHATTY)
  \return                0 on success, others on error
  Read Intel hexfile into memory image. For description of
  Intel hex file format see http://en.wikipedia.org/wiki/Intel_HEX
*/
bool import_file_ihx(const wchar_t *filename, MemoryImage_s *image) {

  FILE      *fp;
#if HEXFILE_DEBUG_MSG
  wchar_t*   werror;
  size_t     werrorlen;
#endif
  // open file to read
  if (!(fp = _wfopen(filename, L"rb"))) {
    MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
    werrorlen = mbstowcs(NULL,strerror(errno),0);
    werror = (wchar_t*)malloc((werrorlen+1)*sizeof(wchar_t));
    mbstowcs(werror,strerror(errno),werrorlen+1);
    Error(L"Failed to open file %s with error [%s]", filename, werror);
    free(werror);
#endif
    return 1;
  }


  //=====================
  // start data import
  //=====================

  char              line[STRLEN], tmp[STRLEN];
  int               linecount = 0, idx, len;
  uint8_t           type, chkRead, chkCalc;
  MEMIMAGE_ADDR_T   address = 0; 
  uint64_t          addrOffset, addrJumpStart;
  int               value = 0;

  // avoid compiler warning (variable not yet used). See https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c
  (void) (addrJumpStart);

  // read data line by line
  addrOffset = 0x0000000000000000;
  while (fgets(line, STRLEN, fp)) {

    // increase line counter
    linecount++;

    // check 1st char (must be ':')
    if (line[0] != ':') {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      Error(L"Line %u in Intel hex record: line does not start with ':'", linecount);
#endif
      return 1;
    }

    // record length (address + data + checksum)
    sprintf(tmp,"0x00");
    strncpy(tmp+2, line+1, 2);
    sscanf(tmp, "%x", &value);
    len = value;
    chkCalc = len;

    // 16b address
    sprintf(tmp,"0x0000");
    strncpy(tmp+2, line+3, 4);
    sscanf(tmp, "%x", &value);
    chkCalc += (uint8_t) (value >> 8);
    chkCalc += (uint8_t)  value;
    address = (uint64_t) (value + addrOffset);    // add offset for >64kB addresses

    // record type
    sprintf(tmp,"0x00");
    strncpy(tmp+2, line+7, 2);
    sscanf(tmp, "%x", &value);
    type = value;
    chkCalc += type;

    // record contains data
    if (type==0) {

      // get data
      idx = 9;                            // start at index 9
      for (int i=0; i<len; i++) {
        
        // get next value
        sprintf(tmp,"0x00");
        strncpy(tmp+2, line+idx, 2);      // get next 2 chars as string
        sscanf(tmp, "%x", &value);        // interpret as hex data
        
        // store data byte in memory image
        MemoryImage_addData(image, address+i, (uint8_t) value);
        
        chkCalc += value;                 // increase checksum
        idx+=2;                           // advance 2 chars in line
      }

    } // type==0

    // EOF indicator
    else if (type==1)
      continue;

    // extended segment addresses not yet supported
    else if (type==2) {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      Error(L"Line %u in Intel hex record: extended segment address type 2 not supported", linecount);
#endif
      return 1;
    }

    // start segment address (only relevant for 80x86 processor, ignore here)
    else if (type==3)
      continue;

    // extended address (=upper 16b of address for following data records)
    else if (type==4) {
      idx = 13;                       // start at index 13
      sprintf(tmp,"0x0000");
      strncpy(tmp+2, line+9, 4);      // get next 4 chars as string
      sscanf(tmp, "%x", &value);      // interpret as hex data
      chkCalc += (uint8_t) (value >> 8);
      chkCalc += (uint8_t)  value;
      addrOffset = ((uint64_t) value) << 16;
    } // type==4

    // start linear address records. Can be ignored, see http://www.keil.com/support/docs/1584/
    else if (type==5)
      continue;

    // unsupported record type -> error
    else {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      Error(L"Line %u in Intel hex record: unsupported type %d", linecount, type);
#endif
      return 1;
    }


    // checksum
    sprintf(tmp,"0x00");
    strncpy(tmp+2, line+idx, 2);
    sscanf(tmp, "%x", &value);
    chkRead = value;

    // assert checksum (0xFF xor (sum over all except record type))
    chkCalc = 255 - chkCalc + 1;                 // calculate 2-complement
    if (chkCalc != chkRead) {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      Error(L"Line %u in Intel hex record: checksum error (0x%02" PRIX8 " vs. 0x%02" PRIX8 ")", linecount, (uint8_t) chkRead, (uint8_t) chkCalc);
#endif
      return 1;
    }

    // debug
    //printf("%ld\n", linecount);
    //fflush(stdout);

  } // while !EOF

  //=====================
  // end data import
  //=====================

  MemoryImage_format(image);
  // close file again
  fclose(fp);
  return 0;
} // import_file_ihx()



/**
  \fn void import_file_txt(const char *filename, MemoryImage_s *image, const uint8_t verbose)

  \param[in]  filename    full name of file to read 
  \param      image       pointer to memory image. Must be initialized. Existing content is overwritten
  \param[in]  verbose     verbosity level (0=MUTE, 1=SILENT, 2=INFORM, 3=CHATTY)
  \return                0 on success, others on error
  Read plain table (address / value) file into image memory.
  Address and value may be decimal (plain numbers) or hexadecimal (starting with '0x').
  Lines starting with '#' are ignored. No syntax check is performed.
*/
bool import_file_txt(const wchar_t *filename, MemoryImage_s *image) {

  FILE      *fp;
#if HEXFILE_DEBUG_MSG
  wchar_t   *werror;
  size_t    werrorlen;
#endif
  // open file to read
  if (!(fp = _wfopen(filename, L"rb"))) {
    MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
    werrorlen = mbstowcs(NULL,strerror(errno),0);
    werror = (wchar_t*)malloc((werrorlen+1)*sizeof(wchar_t));
    mbstowcs(werror,strerror(errno),werrorlen+1);
    Error(L"Failed to open file %s with error [%s]", filename, werror);
    free(werror);
#endif
    return 1;
  }


  //=====================
  // start data import
  //=====================

  char            line[STRLEN];
  int             linecount  = 0;
  char            sAddr[STRLEN], sValue[STRLEN];
  uint64_t        address = 0; 
  unsigned int    value = 0;

  // read data line by line
  while (fgets(line, STRLEN, fp)) {

    // increase line counter
    linecount++;

    // if line starts with '#' ignore as comment
    if (line[0] == '#')
      continue;

    // get address and value as string
    sscanf(line, "%s %s", sAddr, sValue);


    //////////
    // extract address
    //////////

    // if string is in hex format, read it
    if (isHexString(sAddr))
      sscanf(sAddr, "%" SCNx64, &address);

    // if string is in decimal format, read it
    else if (isDecString(sAddr))
      sscanf(sAddr, "%" SCNu64, &address);

    // invalid string format
    else {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      werrorlen = mbstowcs(NULL,sAddr,0);
      werror = (wchar_t*)malloc((werrorlen+1)*sizeof(wchar_t));
      mbstowcs(werror,sAddr,werrorlen+1);
      Error(L"Line %u in table: invalid address '%s'", linecount, werror);
      free(werror);
#endif
      return 1;
    }


    //////////
    // extract value
    //////////

    // if string is in hex format, read it
    if (isHexString(sValue))
      sscanf(sValue, "%x", &value);

    // if string is in decimal format, read it
    else if (isDecString(sValue))
      sscanf(sValue, "%d", &value);

    // invalid string format
    else {
      MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
      werrorlen = mbstowcs(NULL,sValue,0);
      werror = (wchar_t*)malloc((werrorlen+1)*sizeof(wchar_t));
      mbstowcs(werror,sValue,werrorlen+1);
      Error(L"Line %u in table: invalid value '%s'", linecount, werror);
      free(werror);
#endif
      return 1;
    }


    // store data byte in memory image
    MemoryImage_addData(image, (MEMIMAGE_ADDR_T) address, (uint8_t) value);

  } // while !EOF

  //=====================
  // end data import
  //=====================

  MemoryImage_format(image);
  // close file again
  fclose(fp);
  return 0;
} // import_file_txt()



/**
  \fn void import_file_bin(const char *filename, const MEMIMAGE_ADDR_T addrStart, MemoryImage_s *image, const uint8_t verbose)

  \param[in]  filename    full name of file to read 
  \param[in]  addrStart   address offset for binary import
  \param      image       pointer to memory image. Must be initialized. Existing content is overwritten
  \param[in]  verbose     verbosity level (0=MUTE, 1=SILENT, 2=INFORM, 3=CHATTY)
  \return                0 on success, others on error
  Read binary file into memory image. Binary data contains no absolute addresses, just data.
  Therefore a starting address must also be provided.
*/
bool import_file_bin(const wchar_t *filename, const MEMIMAGE_ADDR_T addrStart, MemoryImage_s *image) {

  FILE      *fp;
#if HEXFILE_DEBUG_MSG
  wchar_t*   werror;
  size_t     werrorlen;
#endif
  // open file to read
  if (!(fp = _wfopen(filename, L"rb"))) {
    MemoryImage_free(image);
#if HEXFILE_DEBUG_MSG
    werrorlen = mbstowcs(NULL,strerror(errno),0);
    werror = (wchar_t*)malloc((werrorlen+1)*sizeof(wchar_t));
    mbstowcs(werror,strerror(errno),werrorlen+1);
    Error(L"Failed to open file %s with error [%s]", filename, werror);
    free(werror);
#endif
    return 1;
  }


  //=====================
  // start data import
  //=====================

  // read bytes and store to image
  MEMIMAGE_ADDR_T  address = addrStart;
  uint8_t  value;
  while (!feof(fp)) {
    
    // read next byte
    fread(&value, sizeof(uint8_t), 1, fp);

    // store in memory image
    if (!feof(fp))
      MemoryImage_addData(image, (MEMIMAGE_ADDR_T) address, (uint8_t) value);

    // increment address
    address++;

  } // while !EOF

  //=====================
  // end data import
  //=====================

  MemoryImage_format(image);
  // close file again
  fclose(fp);
  return 0;
} // import_file_bin()


// end of file
